﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;
using VersionComparer.ViewModel;
using VersionComparer.Base;
using VersionComparer.UI.ExtendedTreeView;
using System.Drawing;
using VersionComparer.Images;

namespace VersionComparer
{
    public class ExtendedTreeView : MultiSelectTreeView
    {
        #region fields
        Dictionary<Image, int> images = new Dictionary<Image, int>();
        ThreadManager threadManager = new ThreadManager();
        
        object syncApplyFilter = new object();
        List<object> originalTags = new List<object> ();
        #endregion fields

        #region constants
        const int WM_VSCROLL = 277;
        const int WM_MOUSEWHEEL = 0x020A;
        const int SB_THUMBTRACK = 5;
        private const int TVM_SETEXTENDEDSTYLE = 0x1100 + 44;
        private const int TVM_GETEXTENDEDSTYLE = 0x1100 + 45;
        private const int TVS_EX_DOUBLEBUFFER = 0x0004;
        
        
        #endregion constants

        public event EventHandler Scrolled;
        protected override void WndProc(ref Message m)
        {
           
            //process the message
            base.WndProc(ref m);

            //pass scroll messages onto any linked views
            if (m.Msg == WM_VSCROLL || m.Msg == WM_MOUSEWHEEL)
            {
                Scrolled.Execute(this, new EventArgs());
            }
        }


        #region constructors

        public ExtendedTreeView():base()
        {
            this.ImageList = new System.Windows.Forms.ImageList();
            ImageList.Images.Add(ImageResources.Empty);
            
            DoubleBuffered = true;
            DrawMode = TreeViewDrawMode.Normal;
        }
        #endregion constructors


        private int AddImageToList(Image image)
        {
            int index;
            lock (images)
            {
                
                if (!images.TryGetValue(image, out index))
                {
                    index = this.ImageList.Images.Count;
                    this.ImageList.Images.Add(image);
                    images.Add(image, index);
                }
            }
            return index;
        }
       
        public int AddImage(Image image)
        {
            if (image == null)
                return -1;
            int index;
            bool imageExists;
            lock (images)
            {
                imageExists = images.TryGetValue(image, out index);
            }
            if (!imageExists)
                index = this.ExecuteFunc ( AddImageToList , image);
            return index;
            
        }

        public TreeNode[] GetVisibleNodes()
        {
            return GetVisibleNodes(Nodes).ToArray();
        }
        private List<TreeNode> GetVisibleNodes(TreeNodeCollection nodes)
        {
            List<TreeNode> result = new List<TreeNode>();

            foreach (TreeNode node in nodes)
            {
                if (node.Parent == null || node.Parent.IsExpanded)
                {
                    result.Add(node);
                    result.AddRange(GetVisibleNodes(node.Nodes));
                }
            }
            return result;
        }

        private void ReplaceNodeSync(ComparableItemTreeNode currentNode, ComparableItemTreeNode newNode)
        {
            Stopwatch s = new Stopwatch();
            s.Start();
            BeginUpdate();

            ComparableItemTreeNode selectedNode = SelectedNode as ComparableItemTreeNode;
            object selectedNodeTag = null;
            if ( selectedNode != null )
                selectedNodeTag = selectedNode.Tag;

            newNode.CopyContentsTo(currentNode);
            if (selectedNodeTag != null)
            {
                ComparableItemTreeNode newSelectedNode = ComparableItemTreeNode.NodeFromTag(selectedNodeTag);
                SelectedNode = newSelectedNode;
                if (SelectedNode != null)
                    SelectedNode.EnsureVisible();
            }
            
            EndUpdate();
            double d = s.ElapsedMilliseconds;
            
        }
        private void AddNodeSync(TreeNode node)
        {
            //BeginUpdate();
            Nodes.Add(node);
            //ApplyState(node);
            //EndUpdate();
        }
        private void AddNodesSync(TreeNode []nodes)
        {
            Nodes.AddRange(nodes);
        }
        private void RemoveNodesSync(TreeNode[] nodes)
        {
            foreach ( TreeNode node in nodes )
                Nodes.Remove(node);
        }
        private void GetNodes(List<ComparableItemTreeNode> nodes)
        {
            foreach (ComparableItemTreeNode node in Nodes)
                if (node != null)
                    nodes.Add(node);
            if (nodes.Count > 0)
                nodes.Sort(nodes[0]);
        }

        private void ExecuteSetNodes(ComparableItemTreeNode[] nodes, bool matchById)
        {
            try
            {
                List<ComparableItemTreeNode> nodesToSet = new List<ComparableItemTreeNode>(nodes);
                List<ComparableItemTreeNode> currentNodes = new List<ComparableItemTreeNode>();
                List<ComparableItemTreeNode> nodesToRemove = new List<ComparableItemTreeNode>();
                List<ComparableItemTreeNode> nodesToAdd = new List<ComparableItemTreeNode>();
                Dictionary<ComparableItemTreeNode, ComparableItemTreeNode> nodesToReplace = new Dictionary<ComparableItemTreeNode, ComparableItemTreeNode>();

                this.Execute(GetNodes, currentNodes);
                foreach (var node in nodesToSet)
                {
                    ComparableItemTreeNode relatedNode = currentNodes.FirstOrDefault(x => x.Id == node.Id);
                    if (relatedNode != null)
                    {
                        nodesToReplace.Add(relatedNode, node);
                        currentNodes.Remove(relatedNode);
                    }
                    else
                        nodesToAdd.Add(node);
                }

                foreach (ComparableItemTreeNode nodeToBeReplaced in nodesToReplace.Keys)
                {
                    ComparableItemTreeNode newNode = nodesToReplace[nodeToBeReplaced];
                    this.Execute(ReplaceNodeSync, nodeToBeReplaced, newNode);
                }
                if (!matchById)
                    this.Execute(RemoveNodesSync, currentNodes.ToArray());
                if (nodesToAdd.Count > 0)
                    this.Execute(AddNodesSync, nodesToAdd.ToArray());
                this.Execute(ExpandIfSingle);
            }
            finally
            {
            }
        }
        public void RefreshNode(ComparableItemTreeNode node)
        {
            this.Execute(ExecuteSetNodes, new ComparableItemTreeNode[] { node }, true);
        }
        public void SetNodes ( ComparableItemTreeNode[] nodes)
        {
            this.Execute (ExecuteSetNodes,nodes, false);
        }
        public void ExpandIfSingle()
        {
            if (Nodes.Count == 1)
                Nodes[0].Expand();
        }
       
        public int GetHorizontalScrollPos ()
        {
            return NativeMethods.GetScrollPos(Handle, Orientation.Horizontal);
        }
        public int GetVerticalScrollPos ()
        {
            return NativeMethods.GetScrollPos(Handle, Orientation.Vertical);
        }
        public void SetHorizontalScrollPos(int pos)
        {
            if (GetHorizontalScrollPos() != pos)
            {
                NativeMethods.SetScrollPos(Handle, Orientation.Horizontal, pos, true);
            }

        }
        protected override void OnHandleCreated(EventArgs e)
        {
            NativeMethods.SendMessage(this.Handle, TVM_SETEXTENDEDSTYLE, (IntPtr)TVS_EX_DOUBLEBUFFER, (IntPtr)TVS_EX_DOUBLEBUFFER);

            //SetStyle(ControlStyles.UserPaint, true);
            //SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            //SetStyle(ControlStyles.DoubleBuffer, true);
            //SendMessage(this.Handle, TVM_SETEXTENDEDSTYLE, (IntPtr)TVS_EX_DOUBLEBUFFER, (IntPtr)TVS_EX_DOUBLEBUFFER);

            //SetStyle(ControlStyles.UserPaint, true);
            //SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            //SetStyle(ControlStyles.DoubleBuffer, true);


            base.OnHandleCreated(e);
        }
        public void SynchronizeScrolls(ExtendedTreeView other)
        {

        }
        public void SetVerticalScrollPos(int pos)
        {
            if (GetVerticalScrollPos() != pos)
            {
                NativeMethods.SetScrollPos(Handle, Orientation.Vertical, pos, true);
                NativeMethods.SendMessage(Handle, (uint)WM_VSCROLL, (IntPtr)(SB_THUMBTRACK + 0x10000 * pos), (IntPtr)0);
            }
        }
    }
}
